package mongodb.general.operator.mongoHelper.log.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import mongodb.general.operator.config.Constant;
import mongodb.general.operator.mongoHelper.common.SystemTool;
import mongodb.general.operator.mongoHelper.common.builder.UpdateBuilder;
import mongodb.general.operator.mongoHelper.common.dommin.SlowQuery;
import mongodb.general.operator.mongoHelper.log.FormatUtils;
import mongodb.general.operator.mongoHelper.log.MongoLogHelper;
import mongodb.general.operator.mongoHelper.log.enums.LogTypeEnum;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
@Slf4j
public class MongoLogHelperImpl implements MongoLogHelper {
    /*打印查询语句*/
    @Value("${spring.data.mongodb.print:true}")
    protected Boolean print;
    /*是否保存慢查询记录*/
    @Value("${spring.data.mongodb.slowQuery:true}")
    protected Boolean slowQuery;
    /*超过多少时间为慢查询*/
    @Value("${spring.data.mongodb.slowTime:1000}")
    protected Long slowTime;

    @Autowired
    protected MongoTemplate mongoTemplate;

    @Autowired
    protected MongoConverter mongoConverter;

    protected QueryMapper queryMapper;
    protected UpdateMapper updateMapper;

    @PostConstruct
    public void init() {
        queryMapper = new QueryMapper(mongoConverter);
        updateMapper = new UpdateMapper(mongoConverter);
    }

    /**
     * 插入慢查询
     * @param log
     * @param queryTime
     */
    private void insertSlowQuery(String log, Long queryTime) {
        if (slowQuery) {
            SlowQuery slowQuery = new SlowQuery();
            slowQuery.setQuery(log);
            slowQuery.setTime(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
            slowQuery.setQueryTime(queryTime);
            slowQuery.setSystem(SystemTool.getSystem());
            StackTraceElement stack[] = Thread.currentThread().getStackTrace();
            // 保存堆栈
            String stackStr = "";
            for (int i = 0; i < stack.length; i++) {
                stackStr += stack[i].getClassName() + "." + stack[i].getMethodName() + ":" + stack[i].getLineNumber() + "\n";
            }
            slowQuery.setStack(stackStr);
            mongoTemplate.insert(slowQuery);
        }
    }

    /**
     * 获取集合名称 如果没有Document注解 就使用类首字母小写
     * @param clazz
     * @return
     */
    private String getCollectionName(Class<?> clazz) {
        org.springframework.data.mongodb.core.mapping.Document document = clazz.getAnnotation(org.springframework.data.mongodb.core.mapping.Document.class);
        if (document != null) {
            if (StrUtil.isNotEmpty(document.value())) {
                return document.value();
            }
            if (StrUtil.isNotEmpty(document.collection())) {
                return document.collection();
            }
        }
        return StrUtil.lowerFirst(clazz.getSimpleName());
    }
    public void slowAddAndLog(Long startTime,String logString){
        // 记录慢查询
        Long queryTime = System.currentTimeMillis() - startTime;
        if (queryTime > slowTime) {
            insertSlowQuery(logString, queryTime);
        }
        if (print) {
            // 打印语句
            log.info(logString + "\n执行时间:" + queryTime + "ms");
        }
    }
    @Override
    public void log(Class<?> clazz, Query query, Long startTime, LogTypeEnum logTypeEnum) {
        MongoPersistentEntity<?> entity = mongoConverter.getMappingContext().getPersistentEntity(clazz);
        Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
        Document mappedField = queryMapper.getMappedObject(query.getFieldsObject(), entity);
        Document mappedSort = queryMapper.getMappedObject(query.getSortObject(), entity);
        String logString = "";

        switch (logTypeEnum.getDesc()){
            case "Query":
                logString = "\ndb." + getCollectionName(clazz) + ".find(";

                logString += FormatUtils.bson(mappedQuery.toJson()) + ")";

                if (!query.getFieldsObject().isEmpty()) {
                    logString += ".projection(";
                    logString += FormatUtils.bson(mappedField.toJson()) + ")";
                }

                if (query.isSorted()) {
                    logString += ".sort(";
                    logString += FormatUtils.bson(mappedSort.toJson()) + ")";
                }

                if (query.getLimit() != 0l) {
                    logString += ".limit(" + query.getLimit() + ")";
                }

                if (query.getSkip() != 0l) {
                    logString += ".skip(" + query.getSkip() + ")";
                }
                logString += ";";
                break;
            case "Count":
                logString = "\ndb." + StrUtil.lowerFirst(clazz.getSimpleName()) + ".find(";
                logString += FormatUtils.bson(mappedQuery.toJson()) + ")";
                logString += ".count();";
                break;
            case "Delete":
                logString = "\ndb." + StrUtil.lowerFirst(clazz.getSimpleName()) + ".remove(";
                logString += FormatUtils.bson(mappedQuery.toJson()) + ")";
                logString += ";";
                break;
//            case "Update":
//                break;
            default:
        }
        slowAddAndLog(startTime,logString);
    }


    @Override
    public void logUpdate(Class<?> clazz, Query query, UpdateBuilder updateBuilder, boolean multi, Long startTime) {
        MongoPersistentEntity<?> entity = mongoConverter.getMappingContext().getPersistentEntity(clazz);
        Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
        Document mappedUpdate = updateMapper.getMappedObject(updateBuilder.toUpdate().getUpdateObject(), entity);

        String logString = "\ndb." + StrUtil.lowerFirst(clazz.getSimpleName()) + ".update(";
        logString += FormatUtils.bson(mappedQuery.toJson()) + ",";
        logString += FormatUtils.bson(mappedUpdate.toJson()) + ",";
        logString += FormatUtils.bson("{multi:" + multi + "})");
        logString += ";";

        slowAddAndLog(startTime,logString);
    }

    @Override
    public void logSave(Object object, Long startTime, Boolean isInsert) {
        JSONObject jsonObject = JSONUtil.parseObj(object);
        if (isInsert) {
            jsonObject.remove(Constant.ID);
        }
        String logString = "\ndb." + StrUtil.lowerFirst(object.getClass().getSimpleName()) + ".save(";
        logString += JSONUtil.toJsonPrettyStr(jsonObject);
        logString += ");";
        slowAddAndLog(startTime,logString);
    }

    @Override
    public void logSave(List<?> list, Long startTime) {
        List<JSONObject> cloneList = new ArrayList<>();
        for (Object item : list) {
            JSONObject jsonObject = JSONUtil.parseObj(item);
            jsonObject.remove(Constant.ID);
            cloneList.add(jsonObject);
        }

        Object object = list.get(0);
        String logString = "\ndb." + StrUtil.lowerFirst(object.getClass().getSimpleName()) + ".save(";
        logString += JSONUtil.toJsonPrettyStr(cloneList);
        logString += ");";

        slowAddAndLog(startTime,logString);
    }

}
